package com.yugao.fintech.antelope.auth.oauth2.support;

import cn.hutool.core.util.StrUtil;
import com.yugao.fintech.antelope.auth.model.mq.LoginLogMQ;
import com.yugao.fintech.antelope.auth.model.rqrs.AuthUserUpdateReq;
import com.yugao.fintech.antelope.auth.model.rqrs.ConditionLockUserResp;
import com.yugao.fintech.antelope.auth.oauth2.Authentication;
import com.yugao.fintech.antelope.auth.oauth2.model.RegisteredClient;
import com.yugao.fintech.antelope.auth.service.AuthMqService;
import com.yugao.fintech.antelope.auth.service.LoginUserService;
import com.yugao.fintech.antelope.auth.service.UserService;
import com.yugao.fintech.antelope.base.model.constants.AppType;
import com.yugao.fintech.antelope.base.model.constants.RequestCons;
import com.yugao.fintech.antelope.base.model.constants.UserCons;
import com.yugao.fintech.antelope.base.model.enums.AuthErrorEnum;
import com.yugao.fintech.antelope.base.model.module.auth.LoginUser;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContextHolder;
import com.yugao.fintech.framework.assistant.utils.DateUtils;
import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import com.yugao.fintech.framework.core.http.HttpRequestServletManager;
import com.yugao.fintech.framework.core.utils.IpUtils;
import com.yugao.fintech.framework.core.utils.ServletUtils;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

@Slf4j
@Component
public class AuthHandler {
    @Autowired
    private LoginUserService loginUserService;
    @Autowired
    private AuthMqService authMqService;
    @Autowired
    @Lazy
    private UserService userService;

    public void onSuccess(RegisteredClient client, LoginUser authUser) {
        try {
            AppType appType = client.getAppType();
            if (Objects.nonNull(authUser)) {
                loginUserService.unLockUser(authUser.getUserName(), client);
                log.info("用户：{} 登录成功", authUser.getUserName());

                // 绑定成功即认为登录成功
                HttpRequestServletManager servletManager = new HttpRequestServletManager(ServletUtils.getRequestOfNonNull());
                AuthUserUpdateReq updateReq = AuthUserUpdateReq.builder().userId(authUser.getUserId())
                        .lastLoginDate(new Date()).lastLoginIp(IpUtils.getRequestIp(servletManager))
                        .build();
                userService.updateUser(updateReq, appType);

                // 由于采用多租户方式, 需要用户在登录之后选择登录租户之后才会保存登录租户, 这样登录日志就归属登录的租户下了
            }

        } finally {
            TenantContextHolder.clear();
            ;
        }
    }

    public void onFailure(String account, Authentication authentication, Exception exception) {
        try {
            Optional<Exception> exceptionOpt = Optional.ofNullable(exception);

            log.error("用户：{} 登录失败，异常：{}", account, exceptionOpt.map(Throwable::getLocalizedMessage).orElse("无"));

            // 如果用户连续登录失败一定次数将被锁定
            String willLockedMessage = "";
            String lockedMessage = "";
            if (StringUtils.isNotEmpty(account) && exceptionOpt.isPresent() && exception instanceof BizException) {
                BizException bizException = (BizException) exception;
                ConditionLockUserResp resp;
                if (AuthErrorEnum.USER_USERNAME_OR_PASSWORD_ERROR.equals(bizException.getBaseError())) {
                    resp = loginUserService.conditionLockUser(account, authentication);
                    if (resp.getHasLimitLoginFail()) {
                        if (!resp.getLocked()) {
                            willLockedMessage = exception.getLocalizedMessage() + ", " + AuthErrorEnum.WILL_LOCKED.formatMsg(resp.getSurplusLockedCount());
                        } else {
                            String unLockTime = DateUtils.distance(System.currentTimeMillis(), resp.getUnLockTimestamp(), true);
                            lockedMessage = AuthErrorEnum.LOGIN_LOCKED.formatMsg(unLockTime);
                        }
                    }
                } else if (AuthErrorEnum.LOGIN_LOCKED.equals(bizException.getBaseError())) {
                    lockedMessage = bizException.getMessage();
                }
            }

            // 发送异步日志事件
            HttpServletRequest request = ServletUtils.getRequestOfNonNull();
            String startTimeStr = request.getHeader(RequestCons.Headers.REQUEST_START_TIME);
            LoginLogMQ.MsgPayload loginLog = buildLoginLog(account, authentication.getLoginId(), authentication.getClient().getClientId());
            loginLog.setMessage("登录失败");
            loginLog.setStatus(UserCons.Oauth.LOGIN_SUCCESS);
            loginLog.setUserName(account);
            loginLog.setStatus(UserCons.Oauth.LOGIN_FAIL);
            loginLog.setMessage(willLockedMessage + lockedMessage);
            if (StrUtil.isNotBlank(startTimeStr)) {
                Long endTime = System.currentTimeMillis();
                Long startTime = Long.parseLong(startTimeStr);
                loginLog.setCostTime(endTime - startTime);
            }
            authMqService.send(LoginLogMQ.build(loginLog));

            // 处理错误
            processError(exception, willLockedMessage, lockedMessage);
        } finally {

        }
    }

    private void processError(Exception exception,
                              String willLockedMessage, String lockMessage) {
        Integer code = AuthErrorEnum.TOKEN_EXPIRED.getCode();
        HttpStatus status = AuthErrorEnum.TOKEN_EXPIRED.getStatus();
        String msg = StringUtils.defaultIfEmpty(lockMessage, willLockedMessage);

        if (exception instanceof BizException) {
            BizException authException = (BizException) exception;
            if (Objects.nonNull(authException.getBaseError())) {
                code = authException.getCode();
                status = authException.getStatus();
                if (StringUtils.isEmpty(msg)) {
                    msg = authException.getMessage();
                }
            }
        }
        ServletUtils.getResponse().setStatus(status.value());
        throw new BizException(code, msg);
    }


    /**
     * 处理退出成功方法
     *
     * @param loginUser 登录对象
     */
    public void onLogout(LoginUser loginUser) {
        try {
            log.info("用户：{} 退出成功", loginUser.getUserName());
            LoginLogMQ.MsgPayload loginLog = buildLoginLog(loginUser.getUserName(), loginUser.getLoginId(), loginUser.getClientId());
            loginLog.setMessage("退出成功");
            // 发送异步日志事件
            Long startTime = System.currentTimeMillis();
            Long endTime = System.currentTimeMillis();
            loginLog.setCostTime(endTime - startTime);

            authMqService.send(LoginLogMQ.build(loginLog));
        } finally {
            // 无状态 注意删除 context 上下文的信息
            SecurityContextHolder.clearContext();
        }
    }


    private LoginLogMQ.MsgPayload buildLoginLog(String account, String loginId, String clientId) {
        LoginLogMQ.MsgPayload payload = new LoginLogMQ.MsgPayload();
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequestOfNonNull().getHeader("User-Agent"));
        // 获取客户端操作系统
        String os = userAgent.getOperatingSystem().getName();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();

        payload.setBrowser(browser);
        payload.setOs(os);
        payload.setLoginId(loginId);
        payload.setClientId(clientId);
        payload.setIp(IpUtils.getRequestIp(new HttpRequestServletManager(ServletUtils.getRequestOfNonNull())));
        payload.setTenantId(TenantContextHolder.get().getTenantId());
        payload.setUserName(account);
        return payload;
    }
}
